import Module from "./Module"
import SymbolList from "../SymbolList"
import SweepData from "../data/SweepData"
import CameraData from "../data/CameraData"
import ViewmodeData from "../data/ViewmodeData"
import { RequestRenderTargetCommand, ScreenCaptureCommand, EquirectangularCommand } from "../command/render.command"
import { createBlob, encodeRenderTarget, getXmp } from "../utils/jpg.util"
import { CaptureSnapshotCommand, EquirectSnapshotCommand } from "../command/snapshot.command"
import PanoQualityManager from "../utils/PanoQualityManager"
import RenderLayers from "../utils/RenderLayers"
import f from "../math/7170"

import b from "../math/53931"

import { createObVal } from "../observer/observable.value"
import SnapshotsData from "../data/SnapshotsData"
import Vectors from "../three/Vectors"
import w from "../math/69626"
import { SnapshotErrorMessage } from "../message/snapshot.message"

import { SnapshotCaptureError, SnapshotUploadError } from "../exception/snapshot.exception"

import Logger from "../utils/Logger"
import FloorsViewData from "../data/FloorsViewData"
import { UnlockNavigationCommand, LockNavigationCommand } from "../command/navigate.command"
import Snapshot from "../object/Snapshot"
import funcUtil from "../utils/func.util"
import snapshotStateEnum from "../enum/snapshotState.enum"
import { UploadSnapshotCommand } from "../command/snapshot.command"
import { UnlockViewmodeCommand, LockViewmodeCommand } from "../command/viewmode.command"
import sweepAlignmentEnum from "../enum/sweepAlignment.enum"
import sweepPlacementEnum from "../enum/sweepPlacement.enum"
import resolutionEnum from "../enum/resolution.enum"
import configConst from "../constant/config.const"
import viewmodeEnum, { viewRange } from "../enum/viewmode.enum"
import categoryEnum from "../enum/category.enum"
const R = new Logger("snapshot-editor"),
  k = configConst.tileConfig.uploadIntervalDelay
export default class SnapshotsEditor extends Module {
  engine: any
  renderer: any
  sweepTilingModule: any
  sweepData: any
  viewmodeData: any
  cameraData: any
  floorsViewData: any
  snapshotTarget: any
  snapshotOverlayTarget: any
  static captureProgressFudgeFactor: any
  constructor() {
    super(...arguments), (this.name = "snapshots-editor")
  }
  async init(e, t) {
    ;(this.engine = t),
      ([this.renderer, this.sweepTilingModule] = await Promise.all([
        t.getModuleBySymbol(SymbolList.WEBGL_RENDERER),
        t.getModuleBySymbol(SymbolList.SWEEP_PANO)
      ])),
      ([this.sweepData, this.viewmodeData, this.cameraData, this.floorsViewData] = await Promise.all([
        t.market.waitForData(SweepData),
        t.market.waitForData(ViewmodeData),
        t.market.waitForData(CameraData),
        t.market.waitForData(FloorsViewData),
        t.market.waitForData(SnapshotsData)
      ])),
      ([this.snapshotTarget, this.snapshotOverlayTarget] = await Promise.all([
        t.commandBinder.issueCommandWhenBound(new RequestRenderTargetCommand()),
        t.commandBinder.issueCommandWhenBound(new RequestRenderTargetCommand())
      ])),
      this.bindings.push(
        t.commandBinder.addBinding(CaptureSnapshotCommand, async e => {
          const i = e.onProgress || createObVal(0)
          ;(i.value = 10), t.commandBinder.issueCommand(new LockNavigationCommand()), t.commandBinder.issueCommand(new LockViewmodeCommand())
          const n = await this.queryIdealResolution(e.maxSize, e.aspect)
          this.snapshotTarget.setSize(n.width, n.height),
            this.snapshotOverlayTarget.setSize(n.width, n.height),
            await this.fetchHighestAvailable(!1, e.maxSize, e => (i.value = Math.max(10, e))),
            await funcUtil.waitRun(2 * k),
            await this.takeScreenshot()
          const s = this.sweepData.getSweep(this.sweepData.currentSweep)
          this.viewmodeData.currentMode === viewmodeEnum.Panorama && (await this.sweepTilingModule.resetSweep(s.id)),
            t.commandBinder.issueCommand(new UnlockNavigationCommand()),
            t.commandBinder.issueCommand(new UnlockViewmodeCommand())
          let r = e.category
          s &&
            (s.alignmentType === sweepAlignmentEnum.UNALIGNED || s.placementType === sweepPlacementEnum.MANUAL) &&
            r === categoryEnum.USER &&
            (r = categoryEnum.UNALIGNED)
          const o = this.uploadAndAddSnapshot(r)
            .then(e => e && e.sid)
            .catch(e => {
              throw (R.error(e), this.engine.broadcast(new SnapshotErrorMessage(e)), e)
            })
          return e.waitForUpload ? o : null
        })
      )
    const i = Vectors.FORWARD.clone(),
      n = Vectors.FORWARD.clone()
    this.bindings.push(
      t.commandBinder.addBinding(EquirectSnapshotCommand, async e => {
        const s = e.onProgress || createObVal(0)
        ;(s.value = 10), t.commandBinder.issueCommand(new LockNavigationCommand()), await funcUtil.waitRun(50)
        const r = this.queryIdealEqResolution()
        this.snapshotTarget.setSize(r.width, r.height)
        const o = this.sweepData.getSweep(this.sweepData.currentSweep)
        i.copy(Vectors.FORWARD).applyQuaternion(o.rotation).setY(0), n.copy(Vectors.FORWARD).applyQuaternion(this.cameraData.pose.rotation).setY(0)
        const a = w.k2(i, n) + Math.PI
        await this.fetchHighestAvailable(!0, resolutionEnum.ULTRAHIGH, e => (s.value = Math.max(10, e))),
          await this.takeEquirectangular(a),
          await this.sweepTilingModule.resetSweep(o.id),
          t.commandBinder.issueCommand(new UnlockNavigationCommand())
        const l = getXmp(this.snapshotTarget.width, this.snapshotTarget.height, a, 0),
          c = this.uploadAndAddSnapshot(categoryEnum.PANORAMA, l)
            .then(e => e && e.sid)
            .catch(e => {
              throw (R.error(e), this.engine.broadcast(new SnapshotErrorMessage(e)), e)
            })
        return e.waitForUpload ? c : null
      })
    )
  }
  async uploadAndAddSnapshot(e, t?) {
    const i = this.createSnapshot(e)
    i.state = snapshotStateEnum.CAPTURING
    i.imageBlob = createBlob(await encodeRenderTarget(this.snapshotTarget, t))
    i.state = snapshotStateEnum.UPLOADING
    try {
      return await this.engine.commandBinder.issueCommand(new UploadSnapshotCommand(i))
    } catch (e) {
      i.state = snapshotStateEnum.ERROR
      R.error(e)
      throw new SnapshotUploadError(e as any)
    }
  }
  createSnapshot(e) {
    const t = this.viewmodeData.isInside()
    let i = funcUtil.renderTime(new Date())
    if (t) {
      const e = this.sweepData.getSweep(this.sweepData.currentSweep).name
      e && (i = e)
    }
    const n = new Snapshot()
    return (
      (n.name = i),
      (n.category = e),
      (n.is360 = t && !this.sweepData.currentAlignedSweepObject),
      (n.metadata = {
        cameraMode: this.viewmodeData.currentMode,
        cameraPosition: this.cameraData.pose.position.clone(),
        cameraQuaternion: this.cameraData.pose.rotation.clone(),
        orthoZoom: this.viewmodeData.currentMode === viewmodeEnum.Floorplan ? this.cameraData.zoom() : -1,
        ssZoom: this.cameraData.zoom(),
        scanId: t ? this.sweepData.currentSweep : void 0,
        floorId: this.floorsViewData.currentFloorId,
        floorVisibility: this.floorsViewData.getFloorsVisibility()
      }),
      n
    )
  }
  async queryIdealResolution(e = resolutionEnum.ULTRAHIGH, t) {
    let i = 3840,
      n = i * b.LP
    const s = this.renderer.maxTextureSize
    if (
      (i > s &&
        (R.warn(`The active gl context does not support 4k x 2k equirectangular capture\nCapture is limited to a max size of ${s}`), (i = s), (n = i * b.LP)),
      this.viewmodeData.currentMode === viewmodeEnum.Panorama)
    ) {
      const r = await this.sweepTilingModule.availableResolution(this.sweepData.currentSweepObject, e),
        o = (PanoQualityManager.getPanoSize(r) * (this.cameraData.fovY() * f.MN)) / 90
      ;(i = Math.min(Math.round(o * t), s)), (n = Math.round(i / t))
    }
    return Promise.resolve({ width: i, height: n })
  }
  queryIdealEqResolution() {
    let e = 4096,
      t = 0.5 * e
    if (this.viewmodeData.currentMode !== viewmodeEnum.Panorama) throw new SnapshotCaptureError("Equirectangular capture is only supported in Panorama mode")
    const i = this.renderer.maxTextureSize
    return (
      e > i && R.warn(`The active gl context does not support 4k x 2k equirectangular capture\nCapture is limited to a max size of ${i}`),
      (e = Math.min(i, e)),
      (t = 0.5 * e),
      { width: e, height: t }
    )
  }
  fetchHighestAvailable(e, t = resolutionEnum.ULTRAHIGH, i) {
    if (this.viewmodeData.currentMode === viewmodeEnum.Panorama) {
      const n = this.sweepData.currentSweepObject,
        s = e ? viewRange.FullPanorama : viewRange.CurrentView
      return this.sweepTilingModule.requestResolution(n, t, s, !0).then(e => {
        const t = this.sweepTilingModule.waitForQueue(s, e)
        return i && t.progress(e => i(e * SnapshotsEditor.captureProgressFudgeFactor)), t.nativePromise()
      })
    }
    return i && i(SnapshotsEditor.captureProgressFudgeFactor), Promise.resolve()
  }
  takeScreenshot() {
    const e = RenderLayers.ALL
    e.removeLayers(this.engine.getRenderLayer("grid-underlay")),
      e.removeLayers(this.engine.getRenderLayer("cursor-mesh")),
      e.removeLayers(this.engine.getRenderLayer("current-pano-marker"))
    const t = this.renderer.getScene().scene,
      i = this.renderer.getScene().camera,
      n = i.layers.mask
    return (
      (i.layers.mask = e.mask),
      this.engine.commandBinder.issueCommand(new ScreenCaptureCommand(this.snapshotTarget, t, i)).then(() => {
        i.layers.mask = n
      })
    )
  }
  takeEquirectangular(e) {
    const t = this.sweepData.currentSweep,
      i = this.sweepTilingModule.getRenderer(),
      n = i.useTexture(t),
      s = this.engine.commandBinder.issueCommand(new EquirectangularCommand(this.snapshotTarget, n, e))
    return i.freeTexture(t), s
  }
}
SnapshotsEditor.captureProgressFudgeFactor = 90
